﻿using System;
using System.Collections.Generic;
using Data;
using Data.Realm;
using AIMLbot.Utils;

namespace CsClient
{
    public class Program
    {
        static AgentAPI agent; //Nasz agent, instancja klasy AgentAPI.
        public static WorldParameters cennikSwiata; //Informacje o Świecie.
        static bool gracz; //Czy agent jest sterowany przez użytkownika.
        static private String grupa = "VLuke_Jones";
        static public String komunikatDoWyslania = null;

        #region Komunikacja - odsyłanie do odpowiedniej klasy.

        // Nasza metoda nasłuchująca
        static void Listen(String krzyczacyAgent, String komunikat)
        {
            komunikatDoWyslania = Komunikacja.Listen(krzyczacyAgent, komunikat);
        }

        //wysyłamy komunikat
        public static void Speak(String wiadomosc)
        {
            Komunikacja.Speak(agent, wiadomosc);
            if (ZnanaMapa.znajdzPole(Agent.getPozycjaAgenta()).getEnergia() != 0)
                Recharge();
        }

        #endregion

        #region Funkcje pomocnicze

        //Funkcja pomocnicza zatrzymująca działanie programu na określoną ilość sekund.
        public static void Czekaj(int timeSec)
        {
            System.Threading.Thread.Sleep(1000 * timeSec);
            Look();
            return;
        }

        static public bool standUp = false;

        static void StandUp()
        {
            standUp = true;
            Look();
            bool loop = true;
            while (loop)
            {
                if (Agent.getEnergia() < cennikSwiata.speakCost)
                {
                    Console.WriteLine("Mam za mało energii aby odpowiadać! Ginę!");
                    break;
                }
                if (Program.komunikatDoWyslania != null)
                {
                    Program.Speak(Program.komunikatDoWyslania);
                    Program.komunikatDoWyslania = null;
                }
            }

        }

        //Funkcja pozwalająca na sterowanie agentem za pomocą klawiatury.
        //Zostawiona do celów diagnostycznych!
        static void KeyReader()
        {
            Look();
            bool loop = true;
            while (loop)
            {
                Console.WriteLine("Moja energia: " + Agent.getEnergia());
                if (Agent.czyLepiejUmrzec())
                {
                    Console.WriteLine("Mam za mało energii aby cokolwiek zrobić! Ginę!");
                    break;
                }
                if (ZnanaMapa.najblizszeUkrytePole() == null)
                {
                    Console.WriteLine("Ile moglem, tyle zbadalem! Nie dam rady więcej zbadać!");
                }
                switch (Console.ReadKey().Key)
                {
                    case ConsoleKey.UpArrow: StepForward();
                        break;
                    case ConsoleKey.LeftArrow: RotateLeft();
                        break;
                    case ConsoleKey.RightArrow: RotateRight();
                        break;
                    case ConsoleKey.Enter: String message = Console.ReadLine();
                        Speak(message);
                        break;
                    case ConsoleKey.S: ZnanaMapa.show();
                        break;
                    case ConsoleKey.Q: loop = false;
                        break;
                    default: //Console.Beep();
                        break;
                }
            }
        }

        static public bool czyGracz()
        {
            return gracz;
        }

        #endregion

        static void Main(string[] args)
        {
            //powtarzamy czynnosci az nam się uda
            while (true)
            {
                agent = new AgentAPI(Listen); //tworzymy nowe AgentAPI,
                //podając w parametrze naszą metodę nasłuchującą

                // pobieramy parametry połączenia i agenta z klawiatury
                Console.Write("Wczytano IP serwera.\n");
                String ip = "atlantyda.vm.wmi.amu.edu.pl";

                Console.Write("Wczytano nazwe druzyny.\n");
                String groupname = "VLuke_Jones";

                Console.Write("Wprowadzono haslo.\n");
                String grouppass = "xlpxxf";

                Console.Write("Podaj nazwe swiata: ");
                String worldname = Console.ReadLine();

                Console.Write("Podaj imie: ");
                String imie = Console.ReadLine();

                try
                {
                    //łączymy się z serwerem. Odbieramy parametry świata i wyświetlamy je      
                    cennikSwiata = agent.Connect(ip, 6008, groupname, grouppass,
                        worldname, imie);
                    Console.WriteLine(cennikSwiata.initialEnergy + " - Maksymalna energia");
                    Console.WriteLine(cennikSwiata.maxRecharge + " - Maksymalne doładowanie");
                    Console.WriteLine(cennikSwiata.sightScope + " - Zasięg widzenia");
                    Console.WriteLine(cennikSwiata.hearScope + " - Zasięg słyszenia");
                    Console.WriteLine(cennikSwiata.moveCost + " - Koszt chodzenia");
                    Console.WriteLine(cennikSwiata.rotateCost + " - Koszt obrotu");
                    Console.WriteLine(cennikSwiata.speakCost + " - Koszt mówienia");

                    Agent.SkonstruujAgenta(groupname, imie, cennikSwiata);
                    gracz = false;

                    Console.Write("Wybierz tryb:\n(1)SmartBot\n(2)Klawiatura\n(3)StandUp\n");
                    String tryb = Console.ReadLine();

                    //ustawiamy nasza energie na poczatkowa energie kazdego agenta w danym swiecie
                    if (tryb == "1")
                        Agent.Zachowanie();
                    if (tryb == "2")
                    {
                        gracz = true;
                        KeyReader();
                    }
                    if (tryb == "3")
                        StandUp();
                    //na koncu rozlaczamy naszego agenta
                    try
                    {
                        agent.Disconnect();
                    }
                    catch (Exception e)
                    {
                        Console.WriteLine("Disconnected with problems! " + e.Message);
                    }
                    Console.ReadKey();
                    break;
                }
                //w przypadku mało poważnego błędu, jak podanie złego hasła,
                //rzucany jest wyjątek NonCriticalException; zaczynamy od nowa
                catch (NonCriticalException ex)
                {
                    Console.WriteLine(ex.Message);
                }
                // w przypadku każdego innego wyjątku niż NonCriticalException
                //powinniśmy zakończyć program; taki wyjątek nie powinien się zdarzyć
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                    Console.WriteLine(ex.StackTrace);
                    Console.ReadKey();
                }
            }
        }

        #region Patrzenie i jedzenie

        // ładujemy się
        private static void Recharge()
        {
            int added = agent.Recharge();
            Agent.dodajEnergie(added);
            ZnanaMapa.pobierzEnergie(Agent.getPozycjaAgenta(), added);
            PoleMapy zjadywanePole = ZnanaMapa.znajdzPole(Agent.getPozycjaAgenta());
            Console.WriteLine("Otrzymano " + added + " energii");
            if (zjadywanePole.getEnergia() == 0)
            {
                Wspolrzedne najblizszeZrodlo = ZnanaMapa.znajdzNajblizszaEnergie();
            }
            else 
                if (!(Agent.getEnergia() == cennikSwiata.initialEnergy))
                    Recharge();
        }

        private static int LookOneUp()
        {
            //dostajemy listę pól które widzi nasz agent
            OrientedField[] pola = agent.Look();

            foreach (OrientedField pole in pola)
            {
                if ((pole.x == 0) && (pole.y == 1))
                {
                    int zwrot = Convert.ToInt32(Math.Ceiling(Convert.ToDouble(
                        cennikSwiata.moveCost * (pole.height)) / 100));
                    zwrot += cennikSwiata.moveCost;
                    return zwrot;
                }
            }
            return 0;
        }

        public static void Look()
        {
            //dostajemy listę pól które widzi nasz agent
            OrientedField[] pola = agent.Look();

            //zapisujemy informacje o wszystkich widzianych polach
            foreach (OrientedField pole in pola)
            {
                #region Zapisanie Oglądanych Pól
                switch (Agent.getKierunekPatrzenia())
                {
                    case 0:
                        ZnanaMapa.uaktualnijPole(new Wspolrzedne(Agent.getPozycjaAgenta().getX()
                            + pole.x, Agent.getPozycjaAgenta().getY() + pole.y),
                            Agent.getWysokosc() + pole.height, pole.energy, pole.obstacle);
                        break;
                    case 1:
                        ZnanaMapa.uaktualnijPole(new Wspolrzedne(Agent.getPozycjaAgenta().getX()
                            + pole.y, Agent.getPozycjaAgenta().getY() - pole.x),
                            Agent.getWysokosc() + pole.height, pole.energy, pole.obstacle);
                        break;
                    case 2:
                        ZnanaMapa.uaktualnijPole(new Wspolrzedne(Agent.getPozycjaAgenta().getX()
                            - pole.x, Agent.getPozycjaAgenta().getY() - pole.y),
                            Agent.getWysokosc() + pole.height, pole.energy, pole.obstacle);
                        break;
                    case 3:
                        ZnanaMapa.uaktualnijPole(new Wspolrzedne(Agent.getPozycjaAgenta().getX()
                            - pole.y, Agent.getPozycjaAgenta().getY() + pole.x),
                            Agent.getWysokosc() + pole.height, pole.energy, pole.obstacle);
                        break;
                }
                #endregion
            }

            ZnanaMapa.znajdzNajblizszaEnergie();

            foreach (OrientedField pole in pola)
            {
                if (pole.agent != null)
                    #region Próba komunikacji po zauważeniu "swojego"
                    if (!Agent.czyLepiejIscDoZrodla())
                        if (pole.agent.agentGroup == grupa)
                        {
                            String testowy = pole.agent.agentGroup + "." + pole.agent.agentname;
                            if (Komunikacja.juzRozmawialismy.Contains(testowy))
                                goto koniec;
                            Komunikacja.nazwaRozmowcy = testowy;
                            int xRoz = 0;
                            int yRoz = 0;
                            switch ((int)pole.agent.direction)
                            {
                                case 0:
                                    xRoz = pole.x * (-1);
                                    yRoz = pole.y * (-1);
                                    break;
                                case 1:
                                    xRoz = pole.y;
                                    yRoz = pole.x * (-1);
                                    break;
                                case 2:
                                    xRoz = pole.x;
                                    yRoz = pole.y;
                                    break;
                                case 3:
                                    xRoz = pole.y * (-1);
                                    yRoz = pole.x;
                                    break;
                            }
                            switch (Agent.getKierunekPatrzenia())
                            {
                                case 0:
                                    Komunikacja.pozycjaRozmowcy = new Wspolrzedne
                                        (Agent.getPozycjaAgenta().getX() + pole.x, Agent.getPozycjaAgenta()
                                        .getY() + pole.y);
                                    break;
                                case 1:
                                    Komunikacja.pozycjaRozmowcy = new Wspolrzedne
                                        (Agent.getPozycjaAgenta().getX() + pole.y, Agent.getPozycjaAgenta()
                                        .getY() - pole.x);
                                    break;
                                case 2:
                                    Komunikacja.pozycjaRozmowcy = new Wspolrzedne
                                        (Agent.getPozycjaAgenta().getX() - pole.x, Agent.getPozycjaAgenta()
                                        .getY() - pole.y);
                                    break;
                                case 3:
                                    Komunikacja.pozycjaRozmowcy = new Wspolrzedne
                                        (Agent.getPozycjaAgenta().getX() - pole.y, Agent.getPozycjaAgenta()
                                        .getY() + pole.x);
                                    break;
                            }
                            Komunikacja.kierunekPatrzeniaRozmowcy = (int)pole.agent.direction;
                            Komunikacja.wysokoscRozmowcy = Agent.getWysokosc() + pole.height;
                            int mojkier = Komunikacja.kierunekPatrzeniaRozmowcy;
                            if (mojkier % 2 != 0)
                                mojkier = (mojkier + 2) % 4;
                            Speak("Stoj! Moja pozycja, obrot i mapa to " + xRoz + "_" + yRoz + "_"
                                + (pole.height * (-1)) + "_" + mojkier + ZnanaMapa.toString());
                            Czekaj(5);
                            if (!Komunikacja.juzRozmawialismy.Contains(testowy))
                                Komunikacja.juzRozmawialismy.Add(testowy);
                        }
                    #endregion
            }
            koniec:
            ;
        }

        #endregion

        #region Poruszanie się

        //obracamy się w lewo
        public static void RotateLeft()
        {
            if (!agent.RotateLeft())
                Console.WriteLine("Obrot nie powiodl sie - brak energii");
            else
            {
                Agent.obrocWLewo();
            }
            if (ZnanaMapa.znajdzPole(Agent.getPozycjaAgenta()).getEnergia() != 0)
                Recharge();
            Look();
        }

        //obracamy się w prawo
        public static void RotateRight()
        {
            if (!agent.RotateRight())
                Console.WriteLine("Obrot nie powiodl sie - brak energii");
            else
            {
                Agent.obrocWPrawo();
            }
            if (ZnanaMapa.znajdzPole(Agent.getPozycjaAgenta()).getEnergia() != 0)
                Recharge();
            Look();
        }

        //idziemy do przodu
        public static void StepForward()
        {
            int koszt = LookOneUp();

            if (isFrontStepable())
            {
                if (!agent.StepForward())
                {
                    //Kara za brute force!
                    if (Agent.odejmijEnergie(koszt))
                        Console.WriteLine("Wpadles na cos!!! Koszt: " + koszt);
                    Console.WriteLine("Wykonanie kroku nie powiodlo sie");
                }
                else
                {
                    Agent.przesunDoPrzodu();
                    Agent.odejmijEnergie(koszt);
                }
            }
            if (ZnanaMapa.znajdzPole(Agent.getPozycjaAgenta()).getEnergia() != 0)
                Recharge();
            Look();
        }

        private static bool isFrontStepable()
        {
            OrientedField[] pola = agent.Look();
            foreach (OrientedField of in pola)
            {
                if ((of.x == 0) && (of.y == 1))
                    if ((of.agent != null) || (of.obstacle))
                        return false;
            }
            return true;
        }

        #endregion
    }
}